﻿using DimensionsHelper.LanguageService.Syntax;
using DimensionsHelper.Serialization.CommandLine;
using DimensionsHelper.SourceGeneration.CommandLine;
using DimensionsHelper.SourceGeneration.Constants;
using DimensionsHelper.SourceGeneration.Enumerator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Immutable;
using System.Reflection;

namespace SourceGeneratorTest;

internal class CSharpCompilationHelper
{
    private static readonly Assembly systemRuntimeAssembly = Assembly.Load(new AssemblyName("System.Runtime"));
    private static readonly CSharpParseOptions defaultParseOptions = CreateParseOptions();

    public static CSharpParseOptions CreateParseOptions(LanguageVersion? languageVersion = null) => new(
            kind: SourceCodeKind.Regular,
            documentationMode: DocumentationMode.Parse,
            languageVersion: languageVersion ?? LanguageVersion.CSharp9);


    public static CSharpCompilation CreateCompilation(
        string source,
        MetadataReference[]? addionalReferences = null,
        string assemblyName = "TestAssembly",
        CSharpParseOptions? parseOptions = null)
    {
        var references = new List<MetadataReference>
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Type).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(ArgumentContextAttribute).Assembly.Location),
            MetadataReference.CreateFromFile(systemRuntimeAssembly.Location),
            MetadataReference.CreateFromFile(typeof(SyntaxKinds).Assembly.Location),
        };

        if (addionalReferences != null)
        {
            references.AddRange(addionalReferences);
        }

        parseOptions ??= defaultParseOptions;

        var syntaxTrees = new Microsoft.CodeAnalysis.SyntaxTree[]
        {
            CSharpSyntaxTree.ParseText(source, parseOptions)
        };

        return CSharpCompilation.Create(assemblyName, syntaxTrees, references,
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
    }


    public static CSharpGeneratorDriver CreateArgumentSourceGeneratorDriver(
        Compilation compilation,
        ArgumentSourceGenerator? generator = null)
    {
        generator ??= new ArgumentSourceGenerator();
        var parseOptions = compilation.SyntaxTrees
            .OfType<CSharpSyntaxTree>()
            .Select(tree => tree.Options)
            .FirstOrDefault() ?? defaultParseOptions;

        return CSharpGeneratorDriver.Create(
            new[] { generator.AsSourceGenerator() },
            parseOptions: parseOptions);
    }


    public record ArgumentSourceGeneratorResult
    {
        public required ImmutableArray<GenerationInfo> GenerationInfos { get; init; }

        public required Compilation NewCompilation { get; init; }

        public required ImmutableArray<Diagnostic> Diagnostics { get; init; }

        public required ImmutableArray<GeneratorRunResult> Results { get; init; }
    }


    public static ArgumentSourceGeneratorResult RunArgumentSourceGenerator(Compilation compilation)
    {
        var generationInfos = new List<GenerationInfo>();
        var generator = new ArgumentSourceGenerator
        {
            OnSourceEmitting = generationInfos.Add,
        };
        GeneratorDriver driver = CreateArgumentSourceGeneratorDriver(compilation, generator);
        driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
        var runResult = driver.GetRunResult();

        return new ArgumentSourceGeneratorResult
        {
            Diagnostics = diagnostics,
            NewCompilation = outputCompilation,
            GenerationInfos = generationInfos.ToImmutableArray(),
            Results = runResult.Results,
        };
    }


    public record EnumStringSourceGeneratorResult
    {
        public required ImmutableArray<EnumGenerationInfoBase> GenerationInfos { get; init; }

        public required Compilation NewCompilation { get; init; }

        public required ImmutableArray<Diagnostic> Diagnostics { get; init; }

        public required ImmutableArray<GeneratorRunResult> Results { get; init; }
    }


    public static CSharpGeneratorDriver CreateEnumStringSourceGeneratorDriver(
        Compilation compilation,
        EnumConvertSourceGenerator? generator = null)
    {
        generator ??= new EnumConvertSourceGenerator();
        var parseOptions = compilation.SyntaxTrees
            .OfType<CSharpSyntaxTree>()
            .Select(tree => tree.Options)
            .FirstOrDefault() ?? defaultParseOptions;

        return CSharpGeneratorDriver.Create(
            new[] { generator.AsSourceGenerator() },
            parseOptions: parseOptions);
    }


    public static EnumStringSourceGeneratorResult RunEnumStringSourceGenerator(Compilation compilation)
    {
        var generationInfos = new List<EnumGenerationInfoBase>();
        var generator = new EnumConvertSourceGenerator
        {
            OnSourceEmitting = generationInfos.Add,
        };
        GeneratorDriver driver = CreateEnumStringSourceGeneratorDriver(compilation, generator);
        driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
        var runResult = driver.GetRunResult();

        return new EnumStringSourceGeneratorResult
        {
            Diagnostics = diagnostics,
            NewCompilation = outputCompilation,
            GenerationInfos = generationInfos.ToImmutableArray(),
            Results = runResult.Results,
        };
    }


    public static CSharpGeneratorDriver CreateConstantsSourceGeneratorDriver(
        Compilation compilation,
        ConstantsSourceGenerator? generator = null)
    {
        generator ??= new ConstantsSourceGenerator();
        var parseOptions = compilation.SyntaxTrees
            .OfType<CSharpSyntaxTree>()
            .Select(tree => tree.Options)
            .FirstOrDefault() ?? defaultParseOptions;

        return CSharpGeneratorDriver.Create(
            new[] { generator.AsSourceGenerator() },
            parseOptions: parseOptions);
    }



    public static (ImmutableArray<GeneratorRunResult> Results, ImmutableArray<Diagnostic> Diagnostics, Compilation NewCompilation) RunConstantsSourceGenerator(Compilation compilation)
    {
        var generator = new ConstantsSourceGenerator();
        GeneratorDriver driver = CreateConstantsSourceGeneratorDriver(compilation, generator);
        driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _);
        var runResult = driver.GetRunResult();
        return (runResult.Results, runResult.Diagnostics, outputCompilation);
    }
}
